import { closeAllModals, openContextModal, openModal } from '@mantine/modals';
import { useQuery } from '@tanstack/react-query';
import clsx from 'clsx';
import { MouseEvent, useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { generatePath, Link } from 'react-router';

import styles from './sidebar-playlist-list.module.css';

import { getDraggedItems } from '/@/renderer/components/item-list/helpers/get-dragged-items';
import { ContextMenuController } from '/@/renderer/features/context-menu/context-menu-controller';
import { usePlayer } from '/@/renderer/features/player/context/player-context';
import { playlistsQueries } from '/@/renderer/features/playlists/api/playlists-api';
import { CreatePlaylistForm } from '/@/renderer/features/playlists/components/create-playlist-form';
import {
    LONG_PRESS_PLAY_BEHAVIOR,
    PlayTooltip,
} from '/@/renderer/features/shared/components/play-button-group';
import { usePlayButtonClick } from '/@/renderer/features/shared/hooks/use-play-button-click';
import { SidebarItem } from '/@/renderer/features/sidebar/components/sidebar-item';
import { useDragDrop } from '/@/renderer/hooks/use-drag-drop';
import { AppRoute } from '/@/renderer/router/routes';
import { useCurrentServer, useCurrentServerId } from '/@/renderer/store';
import { Accordion } from '/@/shared/components/accordion/accordion';
import { ActionIcon, ActionIconGroup } from '/@/shared/components/action-icon/action-icon';
import { ButtonProps } from '/@/shared/components/button/button';
import { Group } from '/@/shared/components/group/group';
import { Text } from '/@/shared/components/text/text';
import {
    LibraryItem,
    Playlist,
    PlaylistListSort,
    ServerType,
    Song,
    SortOrder,
} from '/@/shared/types/domain-types';
import { DragOperation, DragTarget } from '/@/shared/types/drag-and-drop';
import { Play } from '/@/shared/types/types';

interface PlaylistRowButtonProps extends Omit<ButtonProps, 'onContextMenu' | 'onPlay'> {
    item: Playlist;
    name: string;
    onContextMenu: (e: MouseEvent<HTMLButtonElement>, item: Playlist) => void;
    to: string;
}

const PlaylistRowButton = ({ item, name, onContextMenu, to }: PlaylistRowButtonProps) => {
    const url = {
        pathname: generatePath(AppRoute.PLAYLISTS_DETAIL_SONGS, { playlistId: to }),
        state: { item },
    };
    const { t } = useTranslation();

    const [isHovered, setIsHovered] = useState(false);

    const { isDraggedOver, isDragging, ref } = useDragDrop<HTMLDivElement>({
        drag: {
            getId: () => {
                const draggedItems = getDraggedItems(item, undefined);
                return draggedItems.map((draggedItem) => draggedItem.id);
            },
            getItem: () => {
                const draggedItems = getDraggedItems(item, undefined);
                return draggedItems;
            },
            itemType: LibraryItem.PLAYLIST,
            operation: [DragOperation.ADD],
            target: DragTarget.PLAYLIST,
        },
        drop: {
            canDrop: (args) => {
                return (
                    args.source.itemType !== undefined &&
                    args.source.type !== DragTarget.PLAYLIST &&
                    (args.source.operation?.includes(DragOperation.ADD) ?? false)
                );
            },
            getData: () => {
                return {
                    id: [to],
                    item: [],
                    itemType: LibraryItem.PLAYLIST,
                    type: DragTarget.PLAYLIST,
                };
            },
            onDrag: () => {
                return;
            },
            onDragLeave: () => {
                return;
            },
            onDrop: (args) => {
                const sourceItemType = args.source.itemType as LibraryItem;
                const sourceIds = args.source.id;

                const modalProps: {
                    albumId?: string[];
                    artistId?: string[];
                    folderId?: string[];
                    genreId?: string[];
                    initialSelectedIds?: string[];
                    playlistId?: string[];
                    songId?: string[];
                } = {
                    initialSelectedIds: [to],
                };

                switch (sourceItemType) {
                    case LibraryItem.ALBUM:
                        modalProps.albumId = sourceIds;
                        break;
                    case LibraryItem.ALBUM_ARTIST:
                    case LibraryItem.ARTIST:
                        modalProps.artistId = sourceIds;
                        break;
                    case LibraryItem.FOLDER:
                        modalProps.folderId = sourceIds;
                        break;
                    case LibraryItem.GENRE:
                        modalProps.genreId = sourceIds;
                        break;
                    case LibraryItem.PLAYLIST:
                        modalProps.playlistId = sourceIds;
                        break;
                    case LibraryItem.PLAYLIST_SONG:
                    case LibraryItem.QUEUE_SONG:
                    case LibraryItem.SONG:
                        if (args.source.item && Array.isArray(args.source.item)) {
                            const songs = args.source.item as Song[];
                            modalProps.songId = songs.map((song) => song.id);
                        } else {
                            modalProps.songId = sourceIds;
                        }
                        break;
                    default:
                        return;
                }

                openContextModal({
                    innerProps: modalProps,
                    modalKey: 'addToPlaylist',
                    size: 'lg',
                    title: t('form.addToPlaylist.title', { postProcess: 'titleCase' }),
                });
            },
        },
        isEnabled: true,
    });

    const player = usePlayer();
    const serverId = useCurrentServerId();

    const handlePlay = useCallback(
        (id: string, type: Play) => {
            player.addToQueueByFetch(serverId, [id], LibraryItem.PLAYLIST, type);
        },
        [player, serverId],
    );

    return (
        <div
            className={clsx(styles.row, {
                [styles.rowDraggedOver]: isDraggedOver,
            })}
            onMouseEnter={() => setIsHovered(true)}
            onMouseLeave={() => setIsHovered(false)}
            ref={ref}
            style={{
                opacity: isDragging ? 0.5 : 1,
            }}
        >
            <SidebarItem
                className={clsx({
                    [styles.rowHover]: isHovered,
                })}
                onContextMenu={(e) => onContextMenu(e, item)}
                to={url}
                variant="subtle"
            >
                {name}
            </SidebarItem>
            {isHovered && <RowControls id={to} onPlay={handlePlay} />}
        </div>
    );
};

const RowControls = ({
    id,
    onPlay,
}: {
    id: string;
    onPlay: (id: string, playType: Play) => void;
}) => {
    const handlePlayNext = usePlayButtonClick({
        onClick: () => {
            onPlay(id, Play.NEXT);
        },
        onLongPress: () => {
            onPlay(id, LONG_PRESS_PLAY_BEHAVIOR[Play.NEXT]);
        },
    });

    const handlePlayNow = usePlayButtonClick({
        onClick: () => {
            onPlay(id, Play.NOW);
        },
        onLongPress: () => {
            onPlay(id, LONG_PRESS_PLAY_BEHAVIOR[Play.NOW]);
        },
    });

    const handlePlayLast = usePlayButtonClick({
        onClick: () => {
            onPlay(id, Play.LAST);
        },
        onLongPress: () => {
            onPlay(id, LONG_PRESS_PLAY_BEHAVIOR[Play.LAST]);
        },
    });

    return (
        <ActionIconGroup className={styles.controls}>
            <PlayTooltip type={Play.NOW}>
                <ActionIcon
                    icon="mediaPlay"
                    iconProps={{
                        size: 'md',
                    }}
                    size="xs"
                    variant="subtle"
                    {...handlePlayNow.handlers}
                    {...handlePlayNow.props}
                />
            </PlayTooltip>
            <PlayTooltip type={Play.NEXT}>
                <ActionIcon
                    icon="mediaPlayNext"
                    iconProps={{
                        size: 'md',
                    }}
                    size="xs"
                    variant="subtle"
                    {...handlePlayNext.handlers}
                    {...handlePlayNext.props}
                />
            </PlayTooltip>
            <PlayTooltip type={Play.LAST}>
                <ActionIcon
                    icon="mediaPlayLast"
                    iconProps={{
                        size: 'md',
                    }}
                    size="xs"
                    variant="subtle"
                    {...handlePlayLast.handlers}
                    {...handlePlayLast.props}
                />
            </PlayTooltip>
        </ActionIconGroup>
    );
};

export const SidebarPlaylistList = () => {
    const player = usePlayer();
    const { t } = useTranslation();
    const server = useCurrentServer();

    const playlistsQuery = useQuery(
        playlistsQueries.list({
            query: {
                sortBy: PlaylistListSort.NAME,
                sortOrder: SortOrder.ASC,
                startIndex: 0,
            },
            serverId: server?.id,
        }),
    );

    const handlePlayPlaylist = useCallback(
        (id: string, playType: Play) => {
            player.addToQueueByFetch(server.id, [id], LibraryItem.PLAYLIST, playType);
        },
        [player, server.id],
    );

    const handleContextMenu = useCallback(
        (e: MouseEvent<HTMLButtonElement>, playlist: Playlist) => {
            e.stopPropagation();
            ContextMenuController.call({
                cmd: { items: [playlist], type: LibraryItem.PLAYLIST },
                event: e,
            });
        },
        [],
    );

    const memoizedItemData = useMemo(() => {
        const base = { handlePlay: handlePlayPlaylist };

        if (!server?.type || !server?.username || !playlistsQuery.data?.items) {
            return { ...base, items: playlistsQuery.data?.items };
        }

        const owned: Array<[boolean, () => void] | Playlist> = [];

        for (const playlist of playlistsQuery.data?.items ?? []) {
            if (!playlist.owner || playlist.owner === server.username) {
                owned.push(playlist);
            }
        }

        return { ...base, items: owned };
    }, [playlistsQuery.data?.items, handlePlayPlaylist, server?.type, server.username]);

    const handleCreatePlaylistModal = (e: MouseEvent<HTMLButtonElement>) => {
        e.stopPropagation();

        openModal({
            children: <CreatePlaylistForm onCancel={() => closeAllModals()} />,
            size: server?.type === ServerType?.NAVIDROME ? 'lg' : 'sm',
            title: t('form.createPlaylist.title', { postProcess: 'titleCase' }),
        });
    };

    return (
        <Accordion.Item value="playlists">
            <Accordion.Control component="div" role="button" style={{ userSelect: 'none' }}>
                <Group justify="space-between" pr="var(--theme-spacing-md)">
                    <Text fw={500}>
                        {t('page.sidebar.playlists', {
                            postProcess: 'titleCase',
                        })}
                    </Text>
                    <Group gap="xs">
                        <ActionIcon
                            icon="add"
                            iconProps={{
                                size: 'lg',
                            }}
                            onClick={handleCreatePlaylistModal}
                            size="xs"
                            tooltip={{
                                label: t('action.createPlaylist', {
                                    postProcess: 'sentenceCase',
                                }),
                            }}
                            variant="subtle"
                        />
                        <ActionIcon
                            component={Link}
                            icon="list"
                            iconProps={{
                                size: 'lg',
                            }}
                            onClick={(e) => e.stopPropagation()}
                            size="xs"
                            to={AppRoute.PLAYLISTS}
                            tooltip={{
                                label: t('action.viewPlaylists', {
                                    postProcess: 'sentenceCase',
                                }),
                            }}
                            variant="subtle"
                        />
                    </Group>
                </Group>
            </Accordion.Control>
            <Accordion.Panel>
                {memoizedItemData?.items?.map((item, index) => (
                    <PlaylistRowButton
                        item={item}
                        key={index}
                        name={item.name}
                        onContextMenu={handleContextMenu}
                        to={item.id}
                    />
                ))}
            </Accordion.Panel>
        </Accordion.Item>
    );
};

export const SidebarSharedPlaylistList = () => {
    const player = usePlayer();
    const { t } = useTranslation();
    const server = useCurrentServer();

    const playlistsQuery = useQuery(
        playlistsQueries.list({
            query: {
                sortBy: PlaylistListSort.NAME,
                sortOrder: SortOrder.ASC,
                startIndex: 0,
            },
            serverId: server?.id,
        }),
    );

    const handlePlayPlaylist = useCallback(
        (id: string, playType: Play) => {
            if (!server?.id) return;
            player.addToQueueByFetch(server.id, [id], LibraryItem.PLAYLIST, playType);
        },
        [player, server.id],
    );

    const handleContextMenu = useCallback(
        (e: MouseEvent<HTMLButtonElement>, playlist: Playlist) => {
            e.stopPropagation();
            ContextMenuController.call({
                cmd: {
                    items: [playlist],
                    type: LibraryItem.PLAYLIST,
                },
                event: e,
            });
        },
        [],
    );

    const memoizedItemData = useMemo(() => {
        const base = { handlePlay: handlePlayPlaylist };

        if (!server?.type || !server?.username || !playlistsQuery.data?.items) {
            return { ...base, items: playlistsQuery.data?.items };
        }

        const shared: Playlist[] = [];

        for (const playlist of playlistsQuery.data?.items ?? []) {
            if (playlist.owner && playlist.owner !== server.username) {
                shared.push(playlist);
            }
        }

        return { ...base, items: shared };
    }, [handlePlayPlaylist, playlistsQuery.data?.items, server?.type, server.username]);

    if (memoizedItemData?.items?.length === 0) {
        return null;
    }

    return (
        <Accordion.Item value="shared-playlists">
            <Accordion.Control>
                <Text fw={500} variant="secondary">
                    {t('page.sidebar.shared', {
                        postProcess: 'titleCase',
                    })}
                </Text>
            </Accordion.Control>
            <Accordion.Panel>
                {memoizedItemData?.items?.map((item, index) => (
                    <PlaylistRowButton
                        item={item}
                        key={index}
                        name={item.name}
                        onContextMenu={handleContextMenu}
                        to={item.id}
                    />
                ))}
            </Accordion.Panel>
        </Accordion.Item>
    );
};
